#include "Asteroid.h"

Asteroid::Asteroid(glm::vec3 position) : GameObject("asteroid", position) {
    setColorMap(Texture::load("game/assets/textures/wall_stone.jpg"));
    setMesh(Mesh::load("game/assets/models/asteroid.obj"));

    int levelSize = Global::getWorld()->LEVEL_SIZE;
    setLocation(glm::vec3(Global::randomFloat(-levelSize, levelSize), Global::randomFloat(-levelSize, levelSize), Global::randomFloat(-levelSize, levelSize)));

    glm::vec3 direction = glm::vec3(Global::randomFloat(-80, 80), Global::randomFloat(-80, 80), Global::randomFloat(-80, 80));
    glm::normalize(direction);

    float speed = Global::randomFloat(0, 0.3);
    velocity = direction * speed;

    mass = 30;

    setCollider(new SphereCollider(this, getMesh()->computeRadius()));
}

Asteroid::~Asteroid() {
    //dtor
}

void shipAsteroidCollision(glm::vec3 normal, float penetration, Asteroid* a, Asteroid* b, Player* player) {
    if(penetration <= 0) {
        return;
    }

    // ship mass = 100, inverse mass = 1/100;
    float shipMass = 0.01f;
    float total = shipMass + a->getInverseMass();

    float moveA = penetration * (shipMass / total);
    float moveB = penetration * (a->getInverseMass() / total);

    player->addLocation(normal * moveA);
    a->addLocation(normal * moveB);
}

void shipAsteroidBounce(glm::vec3 normal, Asteroid* a) {
    // only asteroid change velocity
    float vrel = glm::dot(normal, a->getVelocity());

    if(vrel >= 0) {
        return;
    }

    float numer = -2*vrel;
    glm::vec3 impulse = normal * (numer/a->getInverseMass());
    a->addVelocity(impulse * a->getInverseMass());
}

void Asteroid::onCollision(GameObject* otherGameObject) {
    if(Asteroid* asteroid = dynamic_cast<Asteroid*>(otherGameObject)) {
        // Asteroid on asteroid collision

        glm::vec3 distance = getLocation() - asteroid->getLocation();
        float length = glm::length(distance);

        glm::vec3 multiple = distance / length;

        addLocation(multiple * (getInverseMass() / (getInverseMass() + asteroid->getInverseMass())));

        glm::vec3 impactSpeed = getVelocity() - asteroid->getVelocity();
        float dot = glm::dot(impactSpeed, multiple);

        if(dot >= 0.0f) {
            return;
        }

        glm::vec3 impulse = multiple * ((-2*dot)/(getInverseMass() + asteroid->getInverseMass()));
        addVelocity(impulse * getInverseMass());
        asteroid->addVelocity(impulse * -asteroid->getInverseMass());
    }
    // Asteroid on player collision
    else if(Player* player = dynamic_cast<Player*>(otherGameObject)) {
        glm::vec3 point;
        glm::vec3 normal;
        float penetration;

        MeshCollider* playerCollider = (MeshCollider*) player->getCollider();
        SphereCollider* astroidCollider = (SphereCollider*) getCollider();

        if(CollisionDetector::collide(playerCollider, astroidCollider, point, normal, penetration)) {
            shipAsteroidCollision(normal, penetration, this, asteroid, player);
            shipAsteroidBounce(normal, this);

            Explosion* explosion = new Explosion(point);
            explosion->setLocation(glm::vec3(point));
            explosion->createExplosions();

            Global::getWorld()->addLater.push_back(explosion);
        }
    } else if(Rocket* rocket = dynamic_cast<Rocket*>(otherGameObject)) {
        //When rocket hits asteroid, the rocket and the asteroid will be deleted.
        Global::getWorld()->deleteLater.push_back(rocket);
        Global::getWorld()->deleteLater.push_back(this);
        Global::getWorld()->setAsteroidsLeft(Global::getWorld()->getAsteroidsLeft()-1);
    }
}

void Asteroid::accept(Visitor* visitor) {
    visitor->visit(this);
}

glm::vec3 Asteroid::getVelocity() {
    return velocity;
}

void Asteroid::setVelocity(glm::vec3 velocity) {
    this->velocity = velocity;
}

void Asteroid::addVelocity(glm::vec3 velocity) {
    this->velocity += velocity;
}

float Asteroid::getMass() {
    return mass;
}

float Asteroid::getInverseMass() {
    return 1.0f / mass;
}

void Asteroid::setMass(float mass) {
    this->mass = mass;
}
